<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
  <meta content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" name="viewport"/>
  <meta content="zh-cn" http-equiv="content-language"/>
  <meta content="07 Spring事件常见错误" name="description"/>
  <link href="/static/favicon.png" rel="icon"/>
  <title>
   07 Spring事件常见错误
  </title>
  <link href="/static/index.css" rel="stylesheet"/>
  <link href="/static/highlight.min.css" rel="stylesheet"/>
  <script src="/static/highlight.min.js">
  </script>
  <meta content="Hexo 4.2.0" name="generator"/>
  <script async="" data-website-id="83e5d5db-9d06-40e3-b780-cbae722fdf8c" defer="" src="https://analyze.lianglianglee.com/umami.js">
  </script>
 </head>
 <body>
  <div class="book-container">
   <div class="book-sidebar">
    <div class="book-brand">
     <a href="/">
      <img src="/static/favicon.png"/>
      <span>
       技术文章摘抄
      </span>
     </a>
    </div>
    <div class="book-menu uncollapsible">
     <ul class="uncollapsible">
      <li>
       <a class="current-tab" href="/">
        首页
       </a>
      </li>
      <li>
       <a href="../">
        上一级
       </a>
      </li>
     </ul>
     <ul class="uncollapsible">
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/00%20%e5%af%bc%e8%af%bb%205%e5%88%86%e9%92%9f%e8%bd%bb%e6%9d%be%e4%ba%86%e8%a7%a3Spring%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86.html" id="00 导读 5分钟轻松了解Spring基础知识.md">
        00 导读 5分钟轻松了解Spring基础知识.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/00%20%e5%bc%80%e7%af%87%e8%af%8d%20%e8%b4%b4%e5%bf%83%e2%80%9c%e4%bf%9d%e5%a7%86%e2%80%9dSpring%e7%bd%a2%e5%b7%a5%e4%ba%86%e6%80%8e%e4%b9%88%e5%8a%9e%ef%bc%9f.html" id="00 开篇词 贴心“保姆”Spring罢工了怎么办？.md">
        00 开篇词 贴心“保姆”Spring罢工了怎么办？.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/01%20Spring%20Bean%20%e5%ae%9a%e4%b9%89%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="01 Spring Bean 定义常见错误.md">
        01 Spring Bean 定义常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/02%20Spring%20Bean%20%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="02 Spring Bean 依赖注入常见错误（上）.md">
        02 Spring Bean 依赖注入常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/03%20Spring%20Bean%20%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="03 Spring Bean 依赖注入常见错误（下）.md">
        03 Spring Bean 依赖注入常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/04%20Spring%20Bean%20%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="04 Spring Bean 生命周期常见错误.md">
        04 Spring Bean 生命周期常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/05%20Spring%20AOP%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="05 Spring AOP 常见错误（上）.md">
        05 Spring AOP 常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/06%20Spring%20AOP%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="06 Spring AOP 常见错误（下）.md">
        06 Spring AOP 常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/07%20Spring%e4%ba%8b%e4%bb%b6%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="07 Spring事件常见错误.md">
        07 Spring事件常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/08%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20Core%20%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="08 答疑现场：Spring Core 篇思考题合集.md">
        08 答疑现场：Spring Core 篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/09%20Spring%20Web%20URL%20%e8%a7%a3%e6%9e%90%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="09 Spring Web URL 解析常见错误.md">
        09 Spring Web URL 解析常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/10%20Spring%20Web%20Header%20%e8%a7%a3%e6%9e%90%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="10 Spring Web Header 解析常见错误.md">
        10 Spring Web Header 解析常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/11%20Spring%20Web%20Body%20%e8%bd%ac%e5%8c%96%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="11 Spring Web Body 转化常见错误.md">
        11 Spring Web Body 转化常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/12%20Spring%20Web%20%e5%8f%82%e6%95%b0%e9%aa%8c%e8%af%81%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="12 Spring Web 参数验证常见错误.md">
        12 Spring Web 参数验证常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/13%20Spring%20Web%20%e8%bf%87%e6%bb%a4%e5%99%a8%e4%bd%bf%e7%94%a8%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="13 Spring Web 过滤器使用常见错误（上）.md">
        13 Spring Web 过滤器使用常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/14%20Spring%20Web%20%e8%bf%87%e6%bb%a4%e5%99%a8%e4%bd%bf%e7%94%a8%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="14 Spring Web 过滤器使用常见错误（下）.md">
        14 Spring Web 过滤器使用常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/15%20Spring%20Security%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="15 Spring Security 常见错误.md">
        15 Spring Security 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/16%20Spring%20Exception%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="16 Spring Exception 常见错误.md">
        16 Spring Exception 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/17%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20Web%20%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="17 答疑现场：Spring Web 篇思考题合集.md">
        17 答疑现场：Spring Web 篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/18%20Spring%20Data%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="18 Spring Data 常见错误.md">
        18 Spring Data 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/19%20Spring%20%e4%ba%8b%e5%8a%a1%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="19 Spring 事务常见错误（上）.md">
        19 Spring 事务常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/20%20Spring%20%e4%ba%8b%e5%8a%a1%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="20 Spring 事务常见错误（下）.md">
        20 Spring 事务常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/21%20Spring%20Rest%20Template%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="21 Spring Rest Template 常见错误.md">
        21 Spring Rest Template 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/22%20Spring%20Test%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="22 Spring Test 常见错误.md">
        22 Spring Test 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/23%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20%e8%a1%a5%e5%85%85%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="23 答疑现场：Spring 补充篇思考题合集.md">
        23 答疑现场：Spring 补充篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e5%af%bc%e8%af%bb%205%e5%88%86%e9%92%9f%e8%bd%bb%e6%9d%be%e4%ba%86%e8%a7%a3%e4%b8%80%e4%b8%aaHTTP%e8%af%b7%e6%b1%82%e7%9a%84%e5%a4%84%e7%90%86%e8%bf%87%e7%a8%8b.html" id="导读 5分钟轻松了解一个HTTP请求的处理过程.md">
        导读 5分钟轻松了解一个HTTP请求的处理过程.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e7%9f%a5%e8%af%86%e5%9b%9e%e9%a1%be%20%e7%b3%bb%e7%bb%9f%e6%a2%b3%e7%90%86Spring%e7%bc%96%e7%a8%8b%e9%94%99%e8%af%af%e6%a0%b9%e6%ba%90.html" id="知识回顾 系统梳理Spring编程错误根源.md">
        知识回顾 系统梳理Spring编程错误根源.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e7%bb%93%e6%9d%9f%e8%af%ad%20%e9%97%ae%e9%a2%98%e6%80%bb%e6%af%94%e8%a7%a3%e5%86%b3%e5%8a%9e%e6%b3%95%e5%a4%9a.html" id="结束语 问题总比解决办法多.md">
        结束语 问题总比解决办法多.md
       </a>
      </li>
      <li>
       <a href="/assets/捐赠.md">
        捐赠
       </a>
      </li>
     </ul>
    </div>
   </div>
   <div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseleave="remove_inner()" onmouseover="add_inner()">
    <div class="sidebar-toggle-inner">
    </div>
   </div>
   <div class="off-canvas-content">
    <div class="columns">
     <div class="column col-12 col-lg-12">
      <div class="book-navbar">
       <header class="navbar">
        <section class="navbar-section">
         <a onclick="open_sidebar()">
          <i class="icon icon-menu">
          </i>
         </a>
        </section>
       </header>
      </div>
      <div class="book-content" style="max-width: 960px; margin: 0 auto;
    overflow-x: auto;
    overflow-y: hidden;">
       <div class="book-post">
        <div align="center">
         <a href="https://www.aliyun.com/minisite/goods?userCode=lc4iupk4" target="_blank">
          阿里云2C2G3M 99元/年，老用户也可以哦
         </a>
         <hr/>
        </div>
        <p align="center" id="tip">
        </p>
        <p class="title">
         07 Spring事件常见错误
        </p>
        <div>
         <p>
          你好，我是傅健，这节课我们聊聊Spring事件上的常见错误。
         </p>
         <p>
          前面的几讲中，我们介绍了Spring依赖注入、AOP等核心功能点上的常见错误。而作为Spring 的关键功能支撑，Spring事件是一个相对独立的点。或许你从没有在自己的项目中使用过Spring事件，但是你一定见过它的相关日志。而且在未来的编程实践中，你会发现，一旦你用上了Spring事件，往往完成的都是一些有趣的、强大的功能，例如动态配置。那么接下来我就来讲讲Spring事件上都有哪些常见的错误。
         </p>
         <h2 id="案例1-试图处理并不会抛出的事件">
          案例1：试图处理并不会抛出的事件
         </h2>
         <p>
          Spring事件的设计比较简单。说白了，就是监听器设计模式在Spring中的一种实现，参考下图：
         </p>
         <p>
          <img alt="" src="assets/998134895f5e421d8e1acba0ec46be96.jpg"/>
         </p>
         <p>
          从图中我们可以看出，Spring事件包含以下三大组件。
         </p>
         <ol>
          <li>
           事件（Event）：用来区分和定义不同的事件，在Spring中，常见的如ApplicationEvent和AutoConfigurationImportEvent，它们都继承于java.util.EventObject。
          </li>
          <li>
           事件广播器（Multicaster）：负责发布上述定义的事件。例如，负责发布ApplicationEvent 的ApplicationEventMulticaster就是Spring中一种常见的广播器。
          </li>
          <li>
           事件监听器（Listener）：负责监听和处理广播器发出的事件，例如ApplicationListener就是用来处理ApplicationEventMulticaster发布的ApplicationEvent，它继承于JDK的 EventListener，我们可以看下它的定义来验证这个结论：
          </li>
         </ol>
         <blockquote>
          <p>
           public interface ApplicationListener
           <e applicationevent="" extends="">
            extends EventListener {-
void onApplicationEvent(E event);-
}
           </e>
          </p>
         </blockquote>
         <p>
          当然，虽然在上述组件中，任何一个都是缺一不可的，但是功能模块命名不见得完全贴合上述提及的关键字，例如发布AutoConfigurationImportEvent的广播器就不含有Multicaster字样。它的发布是由AutoConfigurationImportSelector来完成的。
         </p>
         <p>
          对这些基本概念和实现有了一定的了解后，我们就可以开始解析那些常见的错误。闲话少说，我们先来看下面这段基于Spring Boot技术栈的代码：
         </p>
         <pre><code>@Slf4j
@Component
public class MyContextStartedEventListener implements ApplicationListener&lt;ContextStartedEvent&gt; {

  public void onApplicationEvent(final ContextStartedEvent event) {
    log.info("{} received: {}", this.toString(), event);
  }

}
</code></pre>
         <p>
          很明显，这段代码定义了一个监听器MyContextStartedEventListener，试图拦截ContextStartedEvent。因为在很多Spring初级开发者眼中，Spring运转的核心就是一个Context的维护，那么启动Spring自然会启动Context，于是他们是很期待出现类似下面的日志的：
         </p>
         <blockquote>
          <p>
           2021-03-07 07:08:21.197 INFO 2624 — [nio-8080-exec-1] c.s.p.l.e.MyContextStartedEventListener : com.spring.puzzle.class7.example1.MyContextStartedEventListener@d33d5a
           <strong>
            received
           </strong>
           : org.springframework.context.event.
           <strong>
            ContextStartedEvent
           </strong>
           [source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@19b56c0, started on Sun Mar 07 07:07:57 CST 2021]
          </p>
         </blockquote>
         <p>
          但是当我们启动Spring Boot后，会发现并不会拦截到这个事件，如何理解这个错误呢？
         </p>
         <h3 id="案例解析">
          案例解析
         </h3>
         <p>
          在Spring事件运用上，这是一个常见的错误，就是不假思索地认为一个框架只要定义了一个事件，那么一定会抛出来。例如，在本案例中，ContextStartedEvent就是Spring内置定义的事件，而Spring Boot本身会创建和运维Context，表面看起来这个事件的抛出是必然的，但是这个事件一定会在Spring Boot启动时抛出来么？
         </p>
         <p>
          答案明显是否定的，我们首先看下要抛出这个事件需要调用的方法是什么？在Spring Boot中，这个事件的抛出只发生在一处，即位于方法AbstractApplicationContext#start中。
         </p>
         <pre><code>@Override
public void start() {
   getLifecycleProcessor().start();
   publishEvent(new ContextStartedEvent(this));
}
</code></pre>
         <p>
          也就是说，只有上述方法被调用，才会抛出ContextStartedEvent，但是这个方法在Spring Boot启动时会被调用么？我们可以查看Spring启动方法中围绕Context的关键方法调用，代码如下：
         </p>
         <pre><code>public ConfigurableApplicationContext run(String... args) {
      //省略非关键代码
      context = createApplicationContext();
      //省略非关键代码
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      //省略非关键代码 
      return context;
}
</code></pre>
         <p>
          我们发现围绕Context、Spring Boot的启动只做了两个关键工作：创建Context和Refresh Context。其中Refresh的关键代码如下：
         </p>
         <pre><code>protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   ((AbstractApplicationContext) applicationContext).refresh();
}
</code></pre>
         <p>
          很明显，Spring启动最终调用的是AbstractApplicationContext#refresh，并不是 AbstractApplicationContext#start。在这样的残酷现实下，ContextStartedEvent自然不会被抛出，不抛出，自然也不可能被捕获。所以这样的错误也就自然发生了。
         </p>
         <h3 id="问题修正">
          问题修正
         </h3>
         <p>
          针对这个案例，有了源码的剖析，我们可以很快找到问题发生的原因，但是修正这个问题还要去追溯我们到底想要的是什么？我们可以分两种情况来考虑。
         </p>
         <p>
          <strong>
           1. 假设我们是误读了ContextStartedEvent。
          </strong>
         </p>
         <p>
          针对这种情况，往往是因为我们确实想在Spring Boot启动时拦截一个启动事件，但是我们粗略扫视相关事件后，误以为ContextStartedEvent就是我们想要的。针对这种情况，我们只需要把监听事件的类型修改成真正发生的事件即可，例如在本案例中，我们可以修正如下：
         </p>
         <pre><code>@Component
public class MyContextRefreshedEventListener implements ApplicationListener&lt;ContextRefreshedEvent&gt; {

  public void onApplicationEvent(final ContextRefreshedEvent event) {
    log.info("{} received: {}", this.toString(), event);
  }

}
</code></pre>
         <p>
          我们监听ContextRefreshedEvent而非ContextStartedEvent。ContextRefreshedEvent的抛出可以参考方法AbstractApplicationContext#finishRefresh，它本身正好是Refresh操作中的一步。
         </p>
         <pre><code>protected void finishRefresh() {
   //省略非关键代码
   initLifecycleProcessor();
   // Propagate refresh to lifecycle processor first.
   getLifecycleProcessor().onRefresh();
   // Publish the final event.
   publishEvent(new ContextRefreshedEvent(this));
   //省略非关键代码
}
</code></pre>
         <p>
          <strong>
           2. 假设我们就是想要处理ContextStartedEvent。
          </strong>
         </p>
         <p>
          这种情况下，我们真的需要去调用AbstractApplicationContext#start方法。例如，我们可以使用下面的代码来让这个事件抛出：
         </p>
         <pre><code>@RestController
public class HelloWorldController {

    @Autowired
    private AbstractApplicationContext applicationContext;

    @RequestMapping(path = "publishEvent", method = RequestMethod.GET)
    public String notifyEvent(){
        applicationContext.start();       
        return "ok";
    };
}
</code></pre>
         <p>
          我们随便找一处来Autowired一个AbstractApplicationContext，然后直接调用其start()就能让事件抛出来。
         </p>
         <p>
          很明显，这种抛出并不难，但是作为题外话，我们可以思考下为什么要去调用start()呢？start()本身在Spring Boot中有何作用？
         </p>
         <p>
          如果我们去翻阅这个方法，我们会发现start()是org.springframework.context.Lifecycle定义的方法，而它在Spring Boot的默认实现中是去执行所有Lifecycle Bean的启动方法，这点可以参考DefaultLifecycleProcessor#startBeans方法来验证：
         </p>
         <pre><code>private void startBeans(boolean autoStartupOnly) {
   Map&lt;String, Lifecycle&gt; lifecycleBeans = getLifecycleBeans();
   Map&lt;Integer, LifecycleGroup&gt; phases = new HashMap&lt;&gt;();
   lifecycleBeans.forEach((beanName, bean) -&gt; {
      if (!autoStartupOnly || (bean instanceof SmartLifecycle &amp;&amp; ((SmartLifecycle) bean).isAutoStartup())) {
         int phase = getPhase(bean);
         LifecycleGroup group = phases.get(phase);
         if (group == null) {
            group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);
            phases.put(phase, group);
         }
         group.add(beanName, bean);
      }
   });
   if (!phases.isEmpty()) {
      List&lt;Integer&gt; keys = new ArrayList&lt;&gt;(phases.keySet());
      Collections.sort(keys);
      for (Integer key : keys) {
         phases.get(key).start();
      }
   }
}
</code></pre>
         <p>
          说起来比较抽象，我们可以去写一个Lifecycle Bean，代码如下：
         </p>
         <pre><code>@Component
@Slf4j
public class MyLifeCycle implements Lifecycle {

    private volatile boolean running = false;

    @Override
    public void start() {
       log.info("lifecycle start");
       running = true;
    }

    @Override
    public void stop() {
       log.info("lifecycle stop");
       running = false;
    }

    @Override
    public boolean isRunning() {
        return running;
    }

}
</code></pre>
         <p>
          当我们再次运行Spring Boot时，只要执行了AbstractApplicationContext的start()，就会输出上述代码定义的行为：输出LifeCycle start日志。
         </p>
         <p>
          通过这个Lifecycle Bean的使用，AbstractApplicationContext的start要做的事，我们就清楚多了。它和Refresh()不同，Refresh()是初始化和加载所有需要管理的Bean，而start只有在有Lifecycle Bean时才有被调用的价值。那么我们自定义Lifecycle Bean一般是用来做什么呢？例如，可以用它来实现运行中的启停。这里不再拓展，你可以自己做更深入的探索。
         </p>
         <p>
          通过这个案例，我们搞定了第一类错误。而从这个错误中，我们也得出了一个启示：
          <strong>
           当一个事件拦截不了时，我们第一个要查的是拦截的事件类型对不对，执行的代码能不能抛出它。
          </strong>
          把握好这点，也就事半功倍了。
         </p>
         <h2 id="案例2-监听事件的体系不对">
          案例2：监听事件的体系不对
         </h2>
         <p>
          通过案例1的学习，我们可以保证事件的抛出，但是抛出的事件就一定能被我们监听到么？我们再来看这样一个案例，首先上代码：
         </p>
         <pre><code>@Slf4j
@Component
public class MyApplicationEnvironmentPreparedEventListener implements ApplicationListener&lt;ApplicationEnvironmentPreparedEvent &gt; {

    public void onApplicationEvent(final ApplicationEnvironmentPreparedEvent event) {
        log.info("{} received: {}", this.toString(), event);
    }

}
</code></pre>
         <p>
          这里我们试图处理ApplicationEnvironmentPreparedEvent。期待出现拦截事件的日志如下：
         </p>
         <blockquote>
          <p>
           2021-03-07 09:12:08.886 INFO 27064 — [ restartedMain] licationEnvironmentPreparedEventListener : com.spring.puzzle.class7.example2.MyApplicationEnvironmentPreparedEventListener@2b093d received: org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent[source=org.springframework.boot.SpringApplication@122b9e6]
          </p>
         </blockquote>
         <p>
          有了案例1的经验，首先我们就可以查看下这个事件的抛出会不会存在问题。这个事件在Spring中是由EventPublishingRunListener#environmentPrepared方法抛出，代码如下：
         </p>
         <pre><code>@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
   this.initialMulticaster
         .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
</code></pre>
         <p>
          现在我们调试下代码，你会发现这个方法在Spring启动时一定经由SpringApplication#prepareEnvironment方法调用，调试截图如下：
         </p>
         <p>
          <img alt="" src="assets/16a124d3fef34f5dbcf738be5fef393c.jpg"/>
         </p>
         <p>
          表面上看，既然代码会被调用，事件就会抛出，那么我们在最开始定义的监听器就能处理，但是我们真正去运行程序时会发现，效果和案例1是一样的，都是监听器的处理并不执行，即拦截不了。这又是为何？
         </p>
         <h3 id="案例解析-1">
          案例解析
         </h3>
         <p>
          实际上，这是在Spring事件处理上非常容易犯的一个错误，即监听的体系不一致。通俗点说，就是“驴头不对马嘴”。我们首先来看下关于ApplicationEnvironmentPreparedEvent的处理，它相关的两大组件是什么？
         </p>
         <ol>
          <li>
           广播器：这个事件的广播器是EventPublishingRunListener的initialMulticaster，代码参考如下：
          </li>
         </ol>
         <pre><code>public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
   //省略非关键代码
   private final SimpleApplicationEventMulticaster initialMulticaster;

   public EventPublishingRunListener(SpringApplication application, String[] args) {
      //省略非关键代码
      this.initialMulticaster = new SimpleApplicationEventMulticaster();
      for (ApplicationListener&lt;?&gt; listener : application.getListeners()) {
         this.initialMulticaster.addApplicationListener(listener);
      }
   }
 }
</code></pre>
         <ol>
          <li>
           监听器：这个事件的监听器同样位于EventPublishingRunListener中，获取方式参考关键代码行：
          </li>
         </ol>
         <blockquote>
          <p>
           this.initialMulticaster.addApplicationListener(listener);
          </p>
         </blockquote>
         <p>
          如果继续查看代码，我们会发现这个事件的监听器就存储在SpringApplication#Listeners中，调试下就可以找出所有的监听器，截图如下：
         </p>
         <p>
          <img alt="" src="assets/9a29c0f2db8c4d47a538947896b97bde.jpg"/>
         </p>
         <p>
          从中我们可以发现并不存在我们定义的MyApplicationEnvironmentPreparedEventListener，这是为何？
         </p>
         <p>
          还是查看代码，当Spring Boot被构建时，会使用下面的方法去寻找上述监听器：
         </p>
         <blockquote>
          <p>
           setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
          </p>
         </blockquote>
         <p>
          而上述代码最终寻找Listeners的候选者，参考代码 SpringFactoriesLoader#loadSpringFactories中的关键行：
         </p>
         <blockquote>
          <p>
           //下面的FACTORIES_RESOURCE_LOCATION定义为 “META-INF/spring.factories”-
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
          </p>
         </blockquote>
         <p>
          我们可以寻找下这样的文件（spring.factories），确实可以发现类似的定义：
         </p>
         <pre><code>org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
//省略其他监听器 
</code></pre>
         <p>
          说到这里，相信你已经意识到本案例的问题所在。我们定义的监听器并没有被放置在META-INF/spring.factories中，实际上，我们的监听器监听的体系是另外一套，其关键组件如下：
         </p>
         <ol>
          <li>
           广播器：即AbstractApplicationContext#applicationEventMulticaster；
          </li>
          <li>
           监听器：由上述提及的META-INF/spring.factories中加载的监听器以及扫描到的 ApplicationListener类型的Bean共同组成。
          </li>
         </ol>
         <p>
          这样比较后，我们可以得出一个结论：
          <strong>
           我们定义的监听器并不能监听到initialMulticaster广播出的ApplicationEnvironmentPreparedEvent。
          </strong>
         </p>
         <h3 id="问题修正-1">
          问题修正
         </h3>
         <p>
          现在就到了解决问题的时候了，我们可以把自定义监听器注册到initialMulticaster广播体系中，这里提供两种方法修正问题。
         </p>
         <ol>
          <li>
           在构建Spring Boot时，添加MyApplicationEnvironmentPreparedEventListener：
          </li>
         </ol>
         <pre><code>@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        MyApplicationEnvironmentPreparedEventListener myApplicationEnvironmentPreparedEventListener = new MyApplicationEnvironmentPreparedEventListener();
        SpringApplication springApplication = new SpringApplicationBuilder(Application.class).listeners(myApplicationEnvironmentPreparedEventListener).build();
        springApplication.run(args);
    }
}
</code></pre>
         <ol>
          <li>
           使用META-INF/spring.factories，即在/src/main/resources下面新建目录META-INF，然后新建一个对应的spring.factories文件：
          </li>
         </ol>
         <pre><code>org.springframework.context.ApplicationListener=\
com.spring.puzzle.listener.example2.MyApplicationEnvironmentPreparedEventListener
</code></pre>
         <p>
          通过上述两种修改方式，即可完成事件的监听，很明显第二种方式要优于第一种，至少完全用原生的方式去解决，而不是手工实例化一个MyApplicationEnvironmentPreparedEventListener。这点还是挺重要的。
         </p>
         <p>
          反思这个案例的错误，结论就是
          <strong>
           对于事件一定要注意“驴头”（监听器）对上“马嘴”（广播）
          </strong>
          。
         </p>
         <h2 id="案例3-部分事件监听器失效">
          案例3：部分事件监听器失效
         </h2>
         <p>
          通过前面案例的解析，我们可以确保事件在合适的时机被合适的监听器所捕获。但是理想总是与现实有差距，有些时候，我们可能还会发现部分事件监听器一直失效或偶尔失效。这里我们可以写一段代码来模拟偶尔失效的场景，首先我们完成一个自定义事件和两个监听器，代码如下：
         </p>
         <pre><code>public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

@Component
@Order(1)
public class MyFirstEventListener implements ApplicationListener&lt;MyEvent&gt; {

    Random random = new Random();

    @Override
    public void onApplicationEvent(MyEvent event) {
        log.info("{} received: {}", this.toString(), event);
        //模拟部分失效
        if(random.nextInt(10) % 2 == 1)
            throw new RuntimeException("exception happen on first listener");
    }
}

@Component
@Order(2)
public class MySecondEventListener implements ApplicationListener&lt;MyEvent&gt; {
    @Override
    public void onApplicationEvent(MyEvent event) {
        log.info("{} received: {}", this.toString(), event);
    }
}

</code></pre>
         <p>
          这里监听器MyFirstEventListener的优先级稍高，且执行过程中会有50%的概率抛出异常。然后我们再写一个Controller来触发事件的发送：
         </p>
         <pre><code>@RestController
@Slf4j
public class HelloWorldController {

    @Autowired
    private AbstractApplicationContext applicationContext;

    @RequestMapping(path = "publishEvent", method = RequestMethod.GET)
    public String notifyEvent(){
        log.info("start to publish event");
        applicationContext.publishEvent(new MyEvent(UUID.randomUUID()));
        return "ok";
    };
}
</code></pre>
         <p>
          完成这些代码后，我们就可以使用
          <a href="http://localhost:8080/publishEvent" target="_blank">
           http://localhost:8080/publishEvent
          </a>
          来测试监听器的接收和执行了。观察测试结果，我们会发现监听器MySecondEventListener有一半的概率并没有接收到任何事件。可以说，我们使用了最简化的代码模拟出了部分事件监听器偶尔失效的情况。当然在实际项目中，抛出异常这个根本原因肯定不会如此明显，但还是可以借机举一反三的。那么如何理解这个问题呢？
         </p>
         <h3 id="案例解析-2">
          案例解析
         </h3>
         <p>
          这个案例非常简易，如果你稍微有些开发经验的话，大概也能推断出原因：处理器的执行是顺序执行的，在执行过程中，如果一个监听器执行抛出了异常，则后续监听器就得不到被执行的机会了。这里我们可以通过Spring源码看下事件是如何被执行的？
         </p>
         <p>
          具体而言，当广播一个事件，执行的方法参考 SimpleApplicationEventMulticaster#multicastEvent(ApplicationEvent)：
         </p>
         <pre><code>@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   Executor executor = getTaskExecutor();
   for (ApplicationListener&lt;?&gt; listener : getApplicationListeners(event, type)) {
      if (executor != null) {
         executor.execute(() -&gt; invokeListener(listener, event));
      }
      else {
         invokeListener(listener, event);
      }
   }
}
</code></pre>
         <p>
          上述方法通过Event类型等信息调用getApplicationListeners获取了具有执行资格的所有监听器（在本案例中，即为MyFirstEventListener和MySecondEventListener），然后按顺序去执行。最终每个监听器的执行是通过invokeListener()来触发的，调用的是接口方法 ApplicationListener#onApplicationEvent。执行逻辑可参考如下代码：
         </p>
         <pre><code>protected void invokeListener(ApplicationListener&lt;?&gt; listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if (errorHandler != null) {
      try {
         doInvokeListener(listener, event);
      }
      catch (Throwable err) {
         errorHandler.handleError(err);
      }
   }
   else {
      doInvokeListener(listener, event);
   }
}

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
   try {
      listener.onApplicationEvent(event);
   }
   catch (ClassCastException ex) {
        //省略非关键代码
      }
      else {
         throw ex;
      }
   }
}
</code></pre>
         <p>
          这里我们并没有去设置什么org.springframework.util.ErrorHandler，也没有绑定什么Executor 来执行任务，所以针对本案例的情况，我们可以看出：
          <strong>
           最终事件的执行是由同一个线程按顺序来完成的，任何一个报错，都会导致后续的监听器执行不了。
          </strong>
         </p>
         <h3 id="问题修正-2">
          问题修正
         </h3>
         <p>
          怎么解决呢？好办，我提供两种方案给你。
         </p>
         <p>
          <strong>
           1. 确保监听器的执行不会抛出异常。
          </strong>
         </p>
         <p>
          既然我们使用多个监听器，我们肯定是希望它们都能执行的，所以我们一定要保证每个监听器的执行不会被其他监听器影响。基于这个思路，我们修改案例代码如下：
         </p>
         <pre><code>@Component
@Order(1)
public class MyFirstEventListener implements ApplicationListener&lt;MyEvent&gt; {
    @Override
    public void onApplicationEvent(MyEvent event) {
        try {
          // 省略事件处理相关代码
        }catch(Throwable throwable){
            //write error/metric to alert
        }

    }
}
</code></pre>
         <p>
          <strong>
           2. 使用org.springframework.util.ErrorHandler。
          </strong>
         </p>
         <p>
          通过上面的案例解析，我们发现，假设我们设置了一个ErrorHandler，那么就可以用这个ErrorHandler去处理掉异常，从而保证后续事件监听器处理不受影响。我们可以使用下面的代码来修正问题：
         </p>
         <pre><code>SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = applicationContext.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, SimpleApplicationEventMulticaster.class);
    simpleApplicationEventMulticaster.setErrorHandler(TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER);
</code></pre>
         <p>
          其中LOG_AND_SUPPRESS_ERROR_HANDLER的实现如下：
         </p>
         <pre><code>public static final ErrorHandler LOG_AND_SUPPRESS_ERROR_HANDLER = new LoggingErrorHandler();

private static class LoggingErrorHandler implements ErrorHandler {

   private final Log logger = LogFactory.getLog(LoggingErrorHandler.class);

   @Override
   public void handleError(Throwable t) {
      logger.error("Unexpected error occurred in scheduled task", t);
   }
}
</code></pre>
         <p>
          对比下方案1，使用ErrorHandler有一个很大的优势，就是我们不需要在某个监听器中都重复类似下面的代码了：
         </p>
         <pre><code>try {
    //省略事件处理过程
    }catch(Throwable throwable){
    //write error/metric to alert
}
</code></pre>
         <p>
          这么看的话，其实Spring的设计还是很全面的，它考虑了各种各样的情况。但是Spring使用者往往都不会去了解其内部实现，这样就会遇到各种各样的问题。相反，如果你对其实现有所了解的话，也对常见错误有一个感知，则大概率是可以快速避坑的，项目也可以运行得更加平稳顺畅。
         </p>
         <h2 id="重点回顾">
          重点回顾
         </h2>
         <p>
          今天我们粗略地了解了Spring事件处理的基本流程。其实，抛开Spring框架，我们去设计一个通用的事件处理框架，常常也会犯这三种错误：
         </p>
         <ol>
          <li>
           误读事件本身含义；
          </li>
          <li>
           监听错了事件的传播系统；
          </li>
          <li>
           事件处理之间互相影响，导致部分事件处理无法完成。
          </li>
         </ol>
         <p>
          这三种错误正好对应了我们这节课讲解的三个案例。
         </p>
         <p>
          此外，在Spring事件处理过程中，我们也学习到了监听器加载的特殊方式，即使用SPI的方式直接从配置文件META-INF/spring.factories中加载。这种方式或者说思想非常值得你去学习，因为它在许多Java应用框架中都有所使用，例如Dubbo，就是使用增强版的SPI来配置编解码器的。
         </p>
         <h2 id="思考题">
          思考题
         </h2>
         <p>
          在案例3中，我们提到默认的事件执行是在同一个线程中执行的，即事件发布者使用的线程。参考如下日志佐证这个结论：
         </p>
         <blockquote>
          <p>
           2021-03-09 09:10:33.052 INFO 18104 — [nio-8080-exec-1] c.s.p.listener.HelloWorldController : start to publish event-
2021-03-09 09:10:33.055 INFO 18104 — [nio-8080-exec-1] c.s.p.l.example3.MyFirstEventListener : com.spring.puzzle.class7.example3.MyFirstEventListener@18faf0 received: com.spring.puzzle.class7.example3.MyEvent[source=df42b08f-8ee2-44df-a957-d8464ff50c88]
          </p>
         </blockquote>
         <p>
          通过日志可以看出，事件的发布和执行使用的都是nio-8080-exec-1线程，但是在事件比较多时，我们往往希望事件执行得更快些，或者希望事件的执行可以异步化不影响主线程。此时应该怎么做呢？
         </p>
         <p>
          期待在留言区看到你的回复，我们下节课见！
         </p>
        </div>
       </div>
       <div>
        <div id="prePage" style="float: left">
        </div>
        <div id="nextPage" style="float: right">
        </div>
       </div>
      </div>
     </div>
    </div>
    <div class="copyright">
     <hr/>
     <p>
      © 2019 - 2023
      <a href="/cdn-cgi/l/email-protection#dbb7b7b7e2efeaeaebec9bbcb6bab2b7f5b8b4b6" target="_blank">
       Liangliang Lee
      </a>
      .
                    Powered by
      <a href="https://github.com/gin-gonic/gin" target="_blank">
       gin
      </a>
      and
      <a href="https://github.com/kaiiiz/hexo-theme-book" target="_blank">
       hexo-theme-book
      </a>
      .
     </p>
    </div>
   </div>
   <a class="off-canvas-overlay" onclick="hide_canvas()">
   </a>
  </div>
  <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js">
  </script>
  <script>
   (function(){var js = "window['__CF$cv$params']={r:'824e616b09f9e65e',t:'MTY5OTc4Nzk3Mi40MDQwMDA='};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js',document.getElementsByTagName('head')[0].appendChild(_cpo);";var _0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position = 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility = 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi = _0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj = _0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if (document.readyState !== 'loading') {handler();} else if (window.addEventListener) {document.addEventListener('DOMContentLoaded', handler);} else {var prev = document.onreadystatechange || function () {};document.onreadystatechange = function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange = prev;handler();}};}})();
  </script>
  <script crossorigin="anonymous" data-cf-beacon='{"rayId":"824e616b09f9e65e","version":"2023.10.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","b":1}' defer="" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317">
  </script>
 </body>
 <script async="" src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756">
 </script>
 <script src="/static/index.js">
 </script>
</html>
